﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using FizzWare.NBuilder;
using IslandUnit;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Ninject;
using Sample1.Web.Controllers;
using Sample1.Web.Models;

namespace Sample1.Tests
{
    // ReSharper disable InconsistentNaming
    [TestClass]
    public class ProductsControllerTest
    {
        #region Additional test attributes
        // 
        //You can use the following additional attributes as you write your tests:
        //
        //Use ClassInitialize to run code before running the first test in the class
        //[ClassInitialize()]
        //public static void MyClassInitialize(TestContext testContext)
        //{
        //}
        //
        //Use ClassCleanup to run code after all tests in a class have run
        //[ClassCleanup()]
        //public static void MyClassCleanup()
        //{
        //}
        //
        //Use TestInitialize to run code before running each test
        //[TestInitialize()]
        //public void MyTestInitialize()
        //{
        //}
        //
        //Use TestCleanup to run code after each test has run
        //[TestCleanup()]
        //public void MyTestCleanup()
        //{
        //}
        //
        #endregion

        [TestMethod]
        public void Index_Returns_List_Of_Products_Without_IslandUnit()
        {
            // Arrange
            IKernel kernel = new StandardKernel();

            // Create the mock object using Moq
            var productRepositoryMock = new Mock<IProductRepository>();

            // Produce data with NBuilder that will be returned by mock object
            IEnumerable<Product> productsList = Builder<Product>.CreateListOfSize(10).Build();

            // Setup the method of mock object used by SUT, to return something
            // the data produced by NBuilder. In fact, we are creating an STUB.
            productRepositoryMock.Setup(m => m.GetAllProducts()).Returns(productsList);

            // Inform to IoC container: When someone needs an IProductRepository, provides
            // the mocked object that we already setted up
            kernel.Bind<IProductRepository>().ToMethod(m => productRepositoryMock.Object);

            // Act
            var sut = kernel.Get<ProductsController>();
            ViewResult viewResult = sut.Index();

            // Assert
            var viewData = viewResult.ViewData.Model as IEnumerable<Product>;
            Assert.IsNotNull(viewData);
            Assert.AreEqual(10, viewData.Count());
        }

        [TestMethod]
        public void Index_Returns_List_Of_Products_Using_IslandUnit()
        {
            // Arrange
            var isolator = new Isolator();
            isolator.AddDependency<IProductRepository>().WithStubFor(m => m.GetAllProducts()).ReturningWith(10);

            // Act
            var sut = isolator.CreateSut<ProductsController>();
            ViewResult viewResult = sut.Index();

            // Assert
            var viewData = viewResult.ViewData.Model as IEnumerable<Product>;
            Assert.IsNotNull(viewData);
            Assert.AreEqual(10, viewData.Count());
        }

        [TestMethod]
        public void Create_Post_Success_Without_IslandUnit()
        {
            // Arrange
            IKernel kernel = new StandardKernel();

            // Create the mock object using Moq
            var productRepositoryMock = new Mock<IProductRepository>();

            // Inform to IoC container: When someone needs an IProductRepository, provides
            // the mocked object that we already setted up
            kernel.Bind<IProductRepository>().ToMethod(m => productRepositoryMock.Object);

            // Act
            var sut = kernel.Get<ProductsController>();
            ActionResult actionResult = sut.Create(new Product { Name = "Test Name", Description = "Test Description" });

            // Assert
            var result = actionResult as RedirectToRouteResult;
            Assert.IsNotNull(result);
            
            // Ask to mock object to verify if this method has been called once
            // when the SUT were invoked
            productRepositoryMock.Verify(m => m.InsertOrUpdate(It.IsAny<Product>()), Times.Once());
            productRepositoryMock.Verify(m => m.Save(), Times.Once());
        }

        [TestMethod]
        public void Create_Post_Success_Using_IslandUnit()
        {
            // Arrange
            var isolator = new Isolator();

            isolator.AddDependency<IProductRepository>()
                .WithMockFor(m => m.InsertOrUpdate(Any.One<Product>())).HaveToBeCalled(1).And()
                .WithMockFor(m => m.Save()).HaveToBeCalled(1);

            // Act
            var sut = isolator.CreateSut<ProductsController>();
            ActionResult actionResult = sut.Create(new Product { Name = "Test Name", Description = "Test Description" });

            // Assert
            var result = actionResult as RedirectToRouteResult;
            Assert.IsNotNull(result);

            isolator.VerifyMockExpectations();
        }

        [TestMethod]
        public void Create_Post_If_Duplicate_Name_Display_Error_Without_IslandUnit()
        {
            // Arrange
            IKernel kernel = new StandardKernel();

            // Create the mock object using Moq
            var productRepositoryMock = new Mock<IProductRepository>();

            // Setup the method of mock object used by SUT, to return something.
            productRepositoryMock.Setup(m => m.Contains("Name1")).Returns(true);

            // Inform to IoC container: When someone needs an IProductRepository, provides
            // the mocked object that we already setted up
            kernel.Bind<IProductRepository>().ToMethod(m => productRepositoryMock.Object);

            // Act
            var sut = kernel.Get<ProductsController>();
            ActionResult actionResult = sut.Create(new Product { Name = "Name1", Description = "Test Description" });

            // Assert
            var result = actionResult as ViewResult;
            Assert.IsNotNull(result);

            Assert.IsFalse(result.ViewData.ModelState.IsValid);

            var viewData = result.ViewData.Model as Product;
            Assert.IsNotNull(viewData);
            Assert.AreEqual("Name1", viewData.Name);
            Assert.AreEqual("Test Description", viewData.Description);

            // Ask to mock object to verify if this method has been called once
            // when the SUT were invoked
            productRepositoryMock.Verify(m => m.InsertOrUpdate(It.IsAny<Product>()), Times.Never());
            productRepositoryMock.Verify(m => m.Save(), Times.Never());
        }

        [TestMethod]
        public void Create_Post_If_Duplicate_Name_Display_Error_Using_IslandUnit()
        {
            // Arrange
            var isolator = new Isolator();

            isolator.AddDependency<IProductRepository>()
                .WithStubFor(m => m.Contains("Name1")).Returning(true).And()
                .WithMockFor(m => m.InsertOrUpdate(Any.One<Product>())).CannotBeCalled().And()
                .WithMockFor(m => m.Save()).CannotBeCalled();

            // Act
            var sut = isolator.CreateSut<ProductsController>();
            ActionResult actionResult = sut.Create(new Product { Name = "Name1", Description = "Test Description" });

            // Assert
            var result = actionResult as ViewResult;
            Assert.IsNotNull(result);

            Assert.IsFalse(result.ViewData.ModelState.IsValid);

            var viewData = result.ViewData.Model as Product;
            Assert.IsNotNull(viewData);
            Assert.AreEqual("Name1", viewData.Name);
            Assert.AreEqual("Test Description", viewData.Description);

            isolator.VerifyMockExpectations();
        }
        
        [TestMethod]
        public void Edit_Post_If_Duplicate_Name_Display_Error_Without_IslandUnit()
        {
            // Arrange
            IKernel kernel = new StandardKernel();

            // Create the mock object using Moq
            var productRepositoryMock = new Mock<IProductRepository>();

            // Setup the method of mock object used by SUT, to return something.
            productRepositoryMock.Setup(m => m.Contains("Name1", 1)).Returns(true);

            // Inform to IoC container: When someone needs an IProductRepository, provides
            // the mocked object that we already setted up
            kernel.Bind<IProductRepository>().ToMethod(m => productRepositoryMock.Object);

            // Act
            var sut = kernel.Get<ProductsController>();
            ActionResult actionResult = sut.Edit(new Product { ProductId = 1, Name = "Name1", Description = "Test Description" });

            // Assert
            var result = actionResult as ViewResult;
            Assert.IsNotNull(result);

            Assert.IsFalse(result.ViewData.ModelState.IsValid);

            var viewData = result.ViewData.Model as Product;
            Assert.IsNotNull(viewData);
            Assert.AreEqual("Name1", viewData.Name);
            Assert.AreEqual("Test Description", viewData.Description);

            // Ask to mock object to verify if this method has been called once
            // when the SUT were invoked
            productRepositoryMock.Verify(m => m.InsertOrUpdate(It.IsAny<Product>()), Times.Never());
            productRepositoryMock.Verify(m => m.Save(), Times.Never());
        }

        [TestMethod]
        public void Edit_Post_If_Duplicate_Name_Display_Error_Using_IslandUnit()
        {
            // Arrange
            var isolator = new Isolator();

            isolator.AddDependency<IProductRepository>()
                .WithStubFor(m => m.Contains("Name1", 1)).Returning(true).And()
                .WithMockFor(m => m.InsertOrUpdate(Any.One<Product>())).CannotBeCalled().And()
                .WithMockFor(m => m.Save()).CannotBeCalled();

            // Act
            var sut = isolator.CreateSut<ProductsController>();
            ActionResult actionResult = sut.Edit(new Product { ProductId = 1, Name = "Name1", Description = "Test Description" });

            // Assert
            var result = actionResult as ViewResult;
            Assert.IsNotNull(result);

            Assert.IsFalse(result.ViewData.ModelState.IsValid);

            var viewData = result.ViewData.Model as Product;
            Assert.IsNotNull(viewData);
            Assert.AreEqual("Name1", viewData.Name);
            Assert.AreEqual("Test Description", viewData.Description);

            isolator.VerifyMockExpectations();
        }
        
        [TestMethod]
        [ExpectedException(typeof(ArgumentException))]
        public void Edit_Post_If_ProductId_Zero_Throws_Exception()
        {
            // Arrange
            var isolator = new Isolator();
            isolator.AddDependency<IProductRepository>();

            // Act
            var sut = isolator.CreateSut<ProductsController>();
            sut.Edit(new Product());
        }
    }
    // ReSharper restore InconsistentNaming
}
